/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
import { TimeUnit } from "../utils/TimeUnit"
import { List } from "@kit.ArkTS"
import { Route } from "../Route"
import { RealCall } from "./RealCall"
import { Address } from "../Address"
import { RealConnection } from "./RealConnection"
import { ConnectionPool } from "../ConnectionPool"
import { TaskRunner } from "../concurrent/TaskRunner"
import { TaskQueue } from "../concurrent/TaskQueue"
import { Task } from "../concurrent/Task"
import { kotlinRequire } from "../utils/Utils"
import { IllegalArgumentException } from "../Error/error"

/** okHttp name prefix for task and thread names */
const okHttpName = "OkHttp"

/**
 * Returns the current time in nanoseconds.
 * Uses high-resolution time if available, otherwise falls back to Date.now() * 1e6.
 */
export function nanoTime(): number {
  // 使用 Date.now() 转换为纳秒（毫秒 * 1,000,000）
  return Date.now() * 1000000
}

export class RealConnectionPool {
  private taskRunner: TaskRunner
  /** The maximum number of idle connections for each address. */
  private maxIdleConnections: number
  private keepAliveDuration: number
  private timeUnit: TimeUnit

  // 异步锁: 用于保护连接池的并发访问
  private acquireLock: Promise<void> = Promise.resolve()

  /** keep-alive duration in nanoseconds */
  private keepAliveDurationNs: number

  /** Queue for scheduling cleanup tasks */
  private cleanupQueue: TaskQueue

  /** All connections in the pool */
  private connections: Array<RealConnection> = new Array<RealConnection>()

  /** Cleanup task for evicting idle connections */
  private cleanupTask: Task

  constructor(taskRunner: TaskRunner, maxIdleConnections: number, keepAliveDuration: number, timeUnit: TimeUnit) {
    kotlinRequire(keepAliveDuration > 0, `keepAliveDuration <= 0: ${keepAliveDuration}`)

    this.taskRunner = taskRunner
    this.maxIdleConnections = maxIdleConnections
    this.keepAliveDuration = keepAliveDuration
    this.timeUnit = timeUnit

    // Convert keepAliveDuration to nanoseconds
    this.keepAliveDurationNs = this.toNanos(keepAliveDuration, timeUnit)

    // Initialize cleanup queue
    this.cleanupQueue = taskRunner.newQueue()

    // Initialize cleanup task
    this.cleanupTask = {
      name: `${okHttpName} ConnectionPool`,
      runOnce: (): number => {
        return this.cleanup(nanoTime())
      }
    }
  }

  /**
   * Converts a duration from the given time unit to nanoseconds.
   */
  private toNanos(duration: number, unit: TimeUnit): number {
    switch (unit.name()) {
      case "NANOSECONDS":
        return duration
      case "MICROSECONDS":
        return duration * 1000
      case "MILLISECONDS":
        return duration * 1000000
      case "SECONDS":
        return duration * 1000000000
      case "MINUTES":
        return duration * 60 * 1000000000
      case "HOURS":
        return duration * 60 * 60 * 1000000000
      case "DAYS":
        return duration * 24 * 60 * 60 * 1000000000
      default:
        throw new IllegalArgumentException(`Unknown time unit: ${unit.name()}`)
    }
  }

  /**
   * Returns the RealConnectionPool from a ConnectionPool.
   */
  static get(connectionPool: ConnectionPool): RealConnectionPool {
    return connectionPool.delegate
  }

  idleConnectionCount(): number {
    return this.connections.filter(conn => conn.calls.length === 0).length;
  }

  connectionCount(): number {
    return this.connections.length;
  }

  /**
   * Attempts to acquire a recycled connection to [address] for [call]. Returns true if a connection
   * was acquired.
   *
   * If [routes] is non-null these are the resolved routes (ie. IP addresses) for the connection.
   * This is used to coalesce related domains to the same HTTP/2 connection, such as `square.com`
   * and `square.ca`.
   */
  async callAcquirePooledConnection(address: Address, call: RealCall, routes: List<Route> | null, requireMultiplexed: boolean): Promise<boolean> {
    // 使用异步锁确保同一时间只有一个请求在获取连接
    // 关键: 先创建新锁,再等待旧锁,确保锁链不会断开
    let releaseLock: () => void
    const previousLock = this.acquireLock
    this.acquireLock = new Promise<void>(resolve => {
      releaseLock = resolve
    })

    // 等待之前的操作完成
    await previousLock
    try {
      console.log("test:: callAcquirePooledConnection - pool size:", this.connections.length, "address:", address.url.host)
      for (const connection of this.connections) {
        // synchronized(connection) {
        if (requireMultiplexed && !connection.isMultiplexed){
          continue
          // return@synchronized
        }
        if (!await connection.isEligible(address, routes)){
          continue
        } // return@synchronized
        console.log("test:: callAcquirePooledConnection - found eligible connection")
        call.acquireConnectionNoEvents(connection)
        return true
        // }
      }
      console.log("test:: callAcquirePooledConnection - no eligible connection found")
      return false
    } finally {
      // 释放锁,允许下一个请求继续
      releaseLock!()
    }
  }

  put(connection: RealConnection): void {
    // connection.assertThreadHoldsLock()
    this.connections.push(connection);
    this.cleanupQueue.schedule(this.cleanupTask)
  }

  delete(connection: RealConnection): void{
    const index = this.connections.indexOf(connection)
    if (index !== -1) {
      this.connections.splice(index, 1)
    }
  }

  /**
   * Notify this pool that [connection] has become idle. Returns true if the connection has been
   * removed from the pool and should be closed.
   */
  connectionBecameIdle(connection: RealConnection): boolean {
    // connection.assertThreadHoldsLock()
    console.log("okhttp test log :: connectionBecameIdle - noNewExchanges:", connection.getNoNewExchanges(), "maxIdleConnections:", this.maxIdleConnections)
    if (connection.getNoNewExchanges() || this.maxIdleConnections === 0) {
      console.log("okhttp test log :: connectionBecameIdle - removing connection from pool (should close)")
      connection._noNewExchanges = true;
      const index = this.connections.indexOf(connection);
      if (index !== -1) {
        this.connections.splice(index, 1);
      }
      if (this.connections.length === 0) {
        this.cleanupQueue.cancelAll()
      }
      return true;
    } else {
      console.log("okhttp test log :: connectionBecameIdle - connection stays in pool (should NOT close)")
      this.cleanupQueue.schedule(this.cleanupTask)
      return false;
    }
  }

  evictAll(): void {
    // 遍历所有连接，关闭空闲的连接
    const toClose: RealConnection[] = []

    for (let i = this.connections.length - 1; i >= 0; i--) {
      const connection = this.connections[i]
      if (connection.calls.length === 0) {
        this.connections.splice(i, 1)
        connection._noNewExchanges = true
        toClose.push(connection)
      }
    }

    // 关闭连接
    for (const connection of toClose) {
      connection.cancel().catch(() => {
        // 忽略关闭错误
      })
    }

    if (this.connections.length === 0) {
      this.cleanupQueue.cancelAll()
    }
  }

  /**
   * Performs maintenance on this pool, evicting the connection that has been idle the longest if
   * either it has exceeded the keep alive limit or the idle connections limit.
   *
   * Returns the duration in nanoseconds to sleep until the next scheduled call to this method.
   * Returns -1 if no further cleanups are required.
   */
  cleanup(now: number): number {
    let inUseConnectionCount = 0
    let idleConnectionCount = 0
    let longestIdleConnection: RealConnection | null = null
    let longestIdleDurationNs = Number.MIN_SAFE_INTEGER

    // Find either a connection to evict, or the time that the next eviction is due.
    for (const connection of this.connections) {
      // If the connection is in use, keep searching.
      if (this.pruneAndGetAllocationCount(connection, now) > 0) {
        inUseConnectionCount++
      } else {
        idleConnectionCount++

        // If the connection is ready to be evicted, we're done.
        const idleDurationNs = now - connection.idleAtNs
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs
          longestIdleConnection = connection
        }
      }
    }

    if (longestIdleDurationNs >= this.keepAliveDurationNs ||
      idleConnectionCount > this.maxIdleConnections) {
      // We've chosen a connection to evict. Confirm it's still okay to be evict, then close it.
      const connection = longestIdleConnection!
      // Double check: connection should still be idle
      if (connection.calls.length > 0) {
        return 0 // No longer idle.
      }
      if (connection.idleAtNs + longestIdleDurationNs !== now) {
        return 0 // No longer oldest.
      }
      connection._noNewExchanges = true
      const index = this.connections.indexOf(connection)
      if (index !== -1) {
        this.connections.splice(index, 1)
      }

      // Close socket quietly
      connection.cancel().catch(() => {
        // Ignore close errors
      })

      if (this.connections.length === 0) {
        this.cleanupQueue.cancelAll()
      }

      // Clean up again immediately.
      return 0
    } else if (idleConnectionCount > 0) {
      // A connection will be ready to evict soon.
      return this.keepAliveDurationNs - longestIdleDurationNs
    } else if (inUseConnectionCount > 0) {
      // All connections are in use. It'll be at least the keep alive duration 'til we run again.
      return this.keepAliveDurationNs
    } else {
      // No connections, idle or in use.
      return -1
    }
  }

  /**
   * Prunes any leaked calls and then returns the number of remaining live calls on [connection].
   * Calls are leaked if the connection is tracking them but the application code has abandoned
   * them. Leak detection is imprecise and relies on garbage collection.
   *
   * Note: In TypeScript/ArkTS, we don't have WeakReference like Java, so leak detection is limited.
   * We simply return the current call count.
   */
  private pruneAndGetAllocationCount(connection: RealConnection, now: number): number {
    const references = connection.calls
    let i = 0
    while (i < references.length) {
      const reference = references.get(i)

      // In TypeScript/ArkTS, we don't have weak references like Java, so we just check if the
      // reference exists
      if (reference !== null && reference !== undefined) {
        i++
        continue
      }

      // We've discovered a leaked call. This is an application bug.
      const message = `A connection to ${connection.route().address.url} was leaked. ` +
        `Did you forget to close a response body?`
      console.warn(message)

      references.removeByIndex(i)
      connection._noNewExchanges = true

      // If this was the last allocation, the connection is eligible for immediate eviction.
      if (references.length === 0) {
        connection.idleAtNs = now - this.keepAliveDurationNs
        return 0
      }
    }

    return references.length
  }
}
